import time
import numpy as np
from ctypes import *

class AppData(Structure):
    _fields_ =  [ ("frame_start_point", c_ulonglong)
                , ("ppm", c_double)
                , ("dfr", c_double)
    ]
    
class View:
    def __init__(self,inf):
        self.inf = inf
        self.fs = inf.nearFrame(self.inf.now())

    def reset(self,future=0):
        self.fs = self.inf.nearFrame(self.inf.now())
        self.run(future)

    def run(self,n):
        fs = self.fs + int(round(self.inf.SampleRate*self.inf.frame*n))
        self.fs = self.inf.nearFrame(fs)

    def raw(self,s,n):
        fs = self.fs + int(round(self.inf.SampleRate*self.inf.frame*s))
        start = self.inf.nearFrame(fs)
        samples = int(round(self.inf.SampleRate*self.inf.frame*n))+3
        rfd = self.inf.getRfData(start,samples)
        data = np.ctypeslib.as_array(rfd)
        iq = data.view(np.int8).astype(np.float64).view(np.complex128)
        iq /= 127.5
        return iq

    def slot(self,s,l):
        start = self.fs + int(round(s/self.inf.factor))
        return self.inf.getData(start,l)

class Interface:
    def __init__(self,config):
        self.frame = config.frame
        self.default_sample_rate = config.default_sample_rate
        self.multiframe = config.multiframe
        self.chiprate = config.chiprate
        if hasattr(config,"rx"):
            self.rx = config.rx
            self.data = self.rx.appData(AppData)
        else:
            self.data = AppData()
        self.cnt = 0
        self.SampleRate = self.default_sample_rate
        
    def getNearest(self):
        r = self.nearFrame(self.now())
        self.data.frame_start_point = c_ulonglong(r)
        return r
    
    def nearFrame(self,now):
        fstart = int(self.data.frame_start_point)
        n = int(round((now - fstart)/self.frame/self.SampleRate))
        r = int(round(fstart+n*self.frame*self.SampleRate))
        return r
    
    # writepoint means byte pos in buf
    def now(self): 
        if self.rx == None:
            return int(time.time()*self.SampleRate)
        else:
            return self.rx.writePoint()//2

    def getRfData(self,offset,length):
        # print("before now:{} off:{} len:{}".format(self.now(),offset,length))
        while self.now()-offset < length:
            time.sleep(0.01)
            self.cnt += 1
            if (self.cnt%1000)==0:
                print("time out, no packet recv",self.now(),offset,self.cnt)
                break
        self.cnt = 0 
        # print(" after now:{} off:{} len:{}".format(self.now(),offset,length))
        rfd = self.rx.mmap(length*2,offset*2)
        return rfd

    @staticmethod
    def toIQ(rfd):
        data = np.ctypeslib.as_array(rfd)
        iq = data.view(np.int8).astype(np.float64).view(np.complex128)
        iq /= 127.5
        return iq

    def getIQ(self,spos):
        rfd = self.getRfData(spos,self.mfl)
        return self.toIQ(rfd)

    def tuner(self,ppm,dftr):
        self.data.ppm = ppm
        self.data.dfr = dftr 
        return self.rx.tuner(ppm,dftr)
    
    def ppm_raw(self,p):
        ppmf = self.data.ppm
        newppm = p + ppmf
        dfr = self.data.dfr
        self.tuner(newppm,dfr)
    
    def ppm_abs(self,p):
        dfr = self.data.dfr
        self.tuner(p,dfr)
    
    def ppm_t(self,df):
        ppmf = self.data.ppm
        ppm = df/1.92
        newppm = ppm + ppmf
        dfr = self.data.dfr
        self.tuner(newppm,dfr)

    def ppm_f(self,df):
        ppmf = self.data.ppm
        dfr = self.data.dfr
        ppm = df/(1.92*dfr)
        newppm = ppm + ppmf
        self.tuner(newppm,dfr)

    def reset_ppm(self):
        print("reset_ppm")
        self.tuner(0.0,self.fc/1.92e6)

    def init_ppm(self):
        dfr = self.data.dfr
        ppmf = self.data.ppm
        print("init ppm",ppmf,dfr)
        if abs(dfr)<0.5:
            self.reset_ppm()
